﻿#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "quc/lightbutton.h"
#include "quc/switchbutton.h"

#include <QPoint>
#include <QDebug>
#include <QToolBox>
#include <QMessageBox>
#include <QTableWidget>
#include <QTableWidgetItem>

#include <QDir>
#include <QFile>
#include <QTextStream>
#include <QMetaType> //用来注册数据类型

#include<QElapsedTimer> //测量函数运行时间
QElapsedTimer debugTime;

/**************************************************** 主界面 *********************************************************/

MainWindow::MainWindow(QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::MainWindow)                                        //构造主界面
{
  frameLen = 0;                 // 帧信息
  frameHeaderLen = 0;
  frameDataTypeLen = 0;
  checkSumPosOnframeDataType =  -1;
  TimeStampPosOnframeDataType = -1;
  recv_frameCounter = 0;

  XAxis = frameCount;           // X轴绘图类型：帧计数/时间戳
  curveRefreshCycle = 1;        // 曲线刷新周期 10ms(默认)
  clearCurveTiming_3min = 0;
  timingPeriod10ms = 10;
  timing10msCounter= 0;

  isOpenSerialPort = false;     // 串口打开
  isSaveTxtFile    = false;     // 文件保存
  isOpenCurvePlot  = false;     // 绘图
  isStartCurvePlot = false;     // 开始绘图
  isOpenDashBoard  = false;     // 仪表盘
  isOpenHelpUi     = false;     // 帮助面板

  qRegisterMetaType<QVector<quint8>>("QVector<quint8>");    //注册信号与槽的传递参数类型

  ui->setupUi(this);
  this->setFixedSize(this->width(),this->height());
//  qDebug() << "UI线程id" <<QThread::currentThreadId();

/**************************************** 选择面板 ******************************************/
  //数据协议面板(默认显示)
  ui->toolBox->setCurrentIndex(0);
  ui->confirmDataFrame_btn->setChecked(false);

  //串口设置面板
  ui->setBaud_combox->setCurrentIndex(1);
  ui->setDataBits_combox->setCurrentIndex(0);
  ui->setParity_combox->setCurrentIndex(0);
  ui->setStopBits_combox->setCurrentIndex(0);
  ui->switchSerialPort_btn->setChecked(false);
  ui->serialPortStauts_led->setBgColor(QColor("#6f7a7e"));

  //串口线程
  serialPortModule = new mySerialPort();
  serialPortSubThread = new QThread();
  serialPortModule->moveToThread(serialPortSubThread);
  serialPortSubThread->start();
  connect(serialPortSubThread,&QThread::started,serialPortModule,&mySerialPort::serialPortInfoInit);  //线程初始化后 新建串口对象并初始化信息
  connect(ui->setPort_combox,&mySerialCombox::detectSerialPorts,serialPortModule,&mySerialPort::updateAvailableSerialPorts);  //点击串口号下拉框自动检测
  connect(serialPortModule,&mySerialPort::sendAvailableSerialPortName,ui->setPort_combox,&mySerialCombox::updateSerialComboxItem);  //检测后更新串口号
  connect(ui->switchSerialPort_btn,&SwitchButton::checkedChanged,this,&MainWindow::slot_switchSerialPort);  //打开串口
  connect(this,&MainWindow::signal_openSerialPortx,serialPortModule,&mySerialPort::openSerialPortx);  //绑定串口打开信号
  connect(this,&MainWindow::signal_closeSerialPortx,serialPortModule,&mySerialPort::closeSerialPortx);  //绑定关闭串口信号
  connect(serialPortModule,&mySerialPort::isSerialPortxOpenSuccessfully,this,&MainWindow::slot_showSerialPortxOpenStatus);  //显示串口打开状态
  connect(serialPortModule,&mySerialPort::isSerialPortxCloseSuccessfully,this,&MainWindow::slot_showSerialPortxCloseStauts);  //显示串口关闭状态
  connect(this,&MainWindow::signal_sendFrameInfoToSerialSubThread,serialPortModule,&mySerialPort::slot_recvFrameInfoFromUiThread); //同步帧信息
//  connect(serialPortModule,&mySerialPort::signal_sendFrameInfoToUiThread,ui->label_showRecvFrameNum,&QLabel::setText); //显示总帧数

  //绘图功能
  curvePlot = new myCurvePlot();
  ui->curveShow_btn->setCheckable(true);
  connect(curvePlot,&myCurvePlot::signal_clearCurvePlotUi,this,[=](){clearCurveTiming_3min = 0;});
  connect(curvePlot,&myCurvePlot::signal_StartCurvePlot,this,[=](bool on){isStartCurvePlot = on;});
  connect(curvePlot,&myCurvePlot::signal_hideCurvePlotUi,this,[=](){ui->curveShow_btn->setChecked(false);});
  connect(curvePlot,&myCurvePlot::signal_sendCurveRefreshFreq,this,[=](int freq){curveRefreshCycle = 1000 / freq / timingPeriod10ms;});

  //仪表盘
  attDashBoard = new myDashBoard();
  ui->dashboardShow_btn->setCheckable(true);
  connect(attDashBoard,&myDashBoard::signal_hideDashBoardUi,this,[=](){ui->dashboardShow_btn->setChecked(false);});

  //帮助面板
  helpInfo = new help();
  connect(helpInfo,&help::signal_hideHelpUi,this,[=](){isOpenHelpUi = false;});

  //主线程定时器 10ms 任务调度
  runTimer = new QTimer(this);
  runTimer->setInterval(timingPeriod10ms);  //10ms
  connect(runTimer,&QTimer::timeout,this,&MainWindow::slot_taskScheduler);

/****************************************** 表格 *******************************************/
  QStringList headerText;
  headerText << "名称/Save" << "数据类型" << "数值 / Divisor" << "图1" << "图2" << "图3";
  ui->tableWidget->setColumnCount(headerText.count());    //列数
  ui->tableWidget->setHorizontalHeaderLabels(headerText); //行表头名
  ui->tableWidget->verticalHeader()->setVisible(true);   //表头
  ui->tableWidget->horizontalHeader()->setVisible(true);
  ui->tableWidget->setColumnWidth(colName,150);   //列宽
  ui->tableWidget->setColumnWidth(colType,141);
  ui->tableWidget->setColumnWidth(colData,150);
  ui->tableWidget->setColumnWidth(colIsCurve,33);
  ui->tableWidget->setColumnWidth(colIsCurve1,33);
  ui->tableWidget->setColumnWidth(colIsCurve2,33);
  ui->tableWidget->setAlternatingRowColors(true);     //隔行换色
  ui->tableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);   //滚动条
  ui->tableWidget->setItemDelegateForColumn(colType,&comBoxDelegate);   //委托
//  ui->tableWidget->setStyleSheet("QTableWidget::item:selected{background:lightblue}");
  connect(ui->tableWidget,&QTableWidget::itemClicked,this,&MainWindow::slot_clickedItemAction);
  ui->tableWidget->insertRow(0);
  creatItemsARow(0,"Frame_Header","uint8_t(hex)","0xAA");
}

MainWindow::~MainWindow()                                       //主界面析构
{
  /* 结束子线程 并删除 不可使用deletelater(该函数是基于主线程事件循环的，在析构函数调用时，主线程的线程循环已经被终止了！) */
  serialPortSubThread->quit();
  if(serialPortSubThread->wait(5) == false){
      serialPortSubThread->terminate();
  }
  delete serialPortSubThread;
  delete serialPortModule;

  delete helpInfo;
  delete curvePlot;
  delete attDashBoard;

//  qDebug() << "主界面被销毁" <<endl;
  delete ui;
}

void MainWindow::closeEvent(QCloseEvent *event)                 //重写主界面关闭事件
{
  if(ui->switchSerialPort_btn->getChecked())
  {
      QMessageBox::critical(this,"@!!!!","请先关闭串口！");
      event->ignore();
  }
  else if(isSaveTxtFile)
  {
      QMessageBox::critical(this,"@!!!!","请先关闭数据捕获功能！");
      event->ignore();
  }
  else
  {
      event->accept();
  }
}

/*************************************************** 其它设置 *********************************************************/

void MainWindow::on_comboBox_setEndianMode_currentIndexChanged(int index)  //设置大小端模式
{
    CV.updateEndianMode(index);
}

void MainWindow::on_comboBox_setXAxis_currentIndexChanged(int index)       //设置曲线X轴
{
    if(index == 0){
        XAxis = frameCount;
    }else if(index == 1){
        XAxis = TimeStamp;
        if(TimeStampPosOnframeDataType < 0){
            QMessageBox::critical(this,"@!!!!","未定义数据戳或未确认数据帧");
            ui->comboBox_setXAxis->setCurrentIndex(0);
            XAxis = frameCount;
        }
    }else{
        XAxis = frameCount;
    }
}

void MainWindow::slot_clickedItemAction()                       //曲线绘制复选框点击事件
{
//    currentItemIndex = ui->tableWidget->currentIndex();  //获取当前坐标

    //绘图复选框
//    QVector<quint8> isPlotXyz,isPlotxYz,isPlotxyZ;
    isPlotXyz.clear();isPlotxYz.clear();isPlotxyZ.clear();
    for(quint16 i=0; i<ui->tableWidget->rowCount(); i++){
        if(ui->tableWidget->item(i,colIsCurve )->checkState() == Qt::Checked){  isPlotXyz.append(i);  }
        if(ui->tableWidget->item(i,colIsCurve1)->checkState() == Qt::Checked){  isPlotxYz.append(i);  }
        if(ui->tableWidget->item(i,colIsCurve2)->checkState() == Qt::Checked){  isPlotxyZ.append(i);  }
    }
    if(isPlotXyz.size() >= 3){  isEnableCurvePlotCheckBox(isPlotXyz,colIsCurve,false);  }//失能除选中之外的所有复选框
    else{  isEnableCurvePlotCheckBox(isPlotXyz,colIsCurve,true);  }     //使能所有复选框
    if(isPlotxYz.size() >= 3){  isEnableCurvePlotCheckBox(isPlotxYz,colIsCurve1,false);  }//失能除选中之外的所有复选框
    else{  isEnableCurvePlotCheckBox(isPlotxYz,colIsCurve1,true);  }     //使能所有复选框
    if(isPlotxyZ.size() >= 3){  isEnableCurvePlotCheckBox(isPlotxyZ,colIsCurve2,false);  }//失能除选中之外的所有复选框
    else{  isEnableCurvePlotCheckBox(isPlotxyZ,colIsCurve2,true);  }     //使能所有复选框

}

void MainWindow::detectAllCurvePlotCheckBox()                   //检测曲线绘制复选框
{
    // 清除
    for(uint8_t i=0; i<3; i++){
        xIsPlot1.clear(); xIsPlot2.clear(); xIsPlot3.clear();
        xIsPlot1Name.clear(); xIsPlot2Name.clear(); xIsPlot3Name.clear();

        xPlotKeys.clear();
        xPlot1Value[i].clear(); xPlot2Value[i].clear(); xPlot3Value[i].clear();
    }

    // 遍历状态：是否需要曲线绘制
    for(quint16 i=0; i<ui->tableWidget->rowCount(); i++)
    {
        //绘图1 一共3条
        if(ui->tableWidget->item(i,colIsCurve)->checkState() == Qt::Checked){
            xIsPlot1.append(i - frameHeaderLen);  //对应了数据类型的位置
            xIsPlot1Name.append( ui->tableWidget->item(i,colName)->text() );  //画图时的图例
        }
        //绘图2 一共3条
        if(ui->tableWidget->item(i,colIsCurve1)->checkState() == Qt::Checked){
            xIsPlot2.append(i - frameHeaderLen);  //对应了数据类型的位置
            xIsPlot2Name.append( ui->tableWidget->item(i,colName)->text() );  //画图时的图例
        }
        //绘图2 一共3条
        if(ui->tableWidget->item(i,colIsCurve2)->checkState() == Qt::Checked){
            xIsPlot3.append(i - frameHeaderLen);  //对应了数据类型的位置
            xIsPlot3Name.append( ui->tableWidget->item(i,colName)->text() );  //画图时的图例
        }
    }

}

void MainWindow::isEnableCurvePlotCheckBox(QVector<quint8> &plotXxx, MainWindow::FieldColNum colIsCurvex, bool checked) //使/失能曲线绘制复选框
{
    quint8 len = 0;
    if(plotXxx.empty())//全部修改为使能或失能
    {
        for(quint8 i=0; i<ui->tableWidget->rowCount(); i++)
        {
            Qt::ItemFlags f = ui->tableWidget->item(i,colIsCurvex)->flags();
            if(checked){  ui->tableWidget->item(i,colIsCurvex)->setFlags(f | Qt::ItemIsEnabled);  }//使能复选框
            else{ ui->tableWidget->item(i,colIsCurvex)->setFlags(f &~ Qt::ItemIsEnabled);  }  //失能复选框
        }
    }
    else
    {
        for(quint8 i=0; i<ui->tableWidget->rowCount(); i++)
        {
            if( i == plotXxx.at(len)){
                len++;
                if(len >= plotXxx.size()) len = plotXxx.size()-1;  //防止索引越界
            }else{
                Qt::ItemFlags f = ui->tableWidget->item(i,colIsCurvex)->flags();
                if(checked){  ui->tableWidget->item(i,colIsCurvex)->setFlags(f | Qt::ItemIsEnabled);  }//使能复选框
                else{ ui->tableWidget->item(i,colIsCurvex)->setFlags(f &~ Qt::ItemIsEnabled);  }  //失能复选框
            }
        }
    }
}

void MainWindow::detectEulerAnglesToDashBoard()                 //检测是否有欧拉角,有则显示到仪表盘
{
    pitchPosOnframeDataType = -1;
    rollPosOnframeDataType = -1;
    yawPosOnframeDataType = -1;
    QString strEuler;

    // 遍历名称：是否含欧拉角的字符串
    for(quint16 i=0; i<ui->tableWidget->rowCount(); i++)
    {
        //绘图1 一共3条
        strEuler = ui->tableWidget->item(i,colName)->text().toLower();

        if( strEuler == "pitch" )     pitchPosOnframeDataType = i - frameHeaderLen;
        else if( strEuler == "roll")  rollPosOnframeDataType  = i - frameHeaderLen;
        else if( strEuler == "yaw" )  yawPosOnframeDataType   = i - frameHeaderLen;
    }
//    qDebug() << pitchPosOnframeDataType << rollPosOnframeDataType << yawPosOnframeDataType;
}

/*************************************************** 任务调度 *********************************************************/

void MainWindow::slot_taskScheduler()                           //任务调度器
{
//    debugTime.start();

    timing10msCounter++;

    quint8 yPacket = parseOrSaveDataTask();      //10ms 解数据-存文件

    if(timing10msCounter % 100 == 0)   //1s 计时 (不费时 放最开头)
    {
        QTime t =  QTime::fromMSecsSinceStartOfDay(timing10msCounter*10);
        QString str = t.toString("hh:mm:ss");
        ui->label_showTiming->setText(str);
    }

    if(timing10msCounter % 50 == 0)    //500ms 更新表格
    {
        ui->label_showRecvFrameNum->setText(QString::number(recv_frameCounter));
        refreshTableTask(yPacket);
    }

    if(timing10msCounter % 25 == 0)    //250ms 更新仪表盘
    {
        if(isOpenDashBoard && !outStr.empty() )
        {
            bool ok = false;
            float eulerTmp[3] = {0.0f};
            if(pitchPosOnframeDataType != -1)  eulerTmp[0] = outStr.at(pitchPosOnframeDataType).toFloat(&ok);
            if(rollPosOnframeDataType  != -1)  eulerTmp[1] = outStr.at(rollPosOnframeDataType).toFloat(&ok);
            if(yawPosOnframeDataType   != -1)  eulerTmp[2] = outStr.at(yawPosOnframeDataType).toFloat(&ok);
            attDashBoard->setValue(eulerTmp[0],eulerTmp[1],eulerTmp[2]);
        }
    }

    if(isOpenCurvePlot)         //10ms 刷新波形
    {
        if(yPacket == 0) return;

        xIsPlotPacketCount = recv_frameCounter - yPacket + 1;
        //根据选中的个数存数据(最多九条曲线) 根据选中状态抽数据 与解包周期一致
        for(quint8 i=0; i<yPacket; ++i)
        {
            for(quint8 m=0; m<xIsPlot1.size(); m++){
                xPlot1Value[m].append(outStr.at( xIsPlot1.at(m) + i*frameDataTypeLen ).toDouble());
            }
            for(quint8 m=0; m<xIsPlot2.size(); m++){
                xPlot2Value[m].append(outStr.at( xIsPlot2.at(m) + i*frameDataTypeLen ).toDouble());
            }
            for(quint8 m=0; m<xIsPlot3.size(); m++){
                xPlot3Value[m].append(outStr.at( xIsPlot3.at(m) + i*frameDataTypeLen ).toDouble());
            }

            if(XAxis == TimeStamp){    // 横轴为时间戳
                xPlotKeys.append(outStr.at( TimeStampPosOnframeDataType + i*frameDataTypeLen ).toDouble());
            }else{                     // 横轴为帧计数
                xPlotKeys.append(xIsPlotPacketCount); xIsPlotPacketCount++;
            }
        }

        if( (timing10msCounter % curveRefreshCycle == 0) && isStartCurvePlot )
        {
            curvePlot->plot1_AddData(xPlotKeys,xPlot1Value[0],xPlot1Value[1],xPlot1Value[2],xIsPlot1Name);
            curvePlot->plot2_AddData(xPlotKeys,xPlot2Value[0],xPlot2Value[1],xPlot2Value[2],xIsPlot2Name);
            curvePlot->plot3_AddData(xPlotKeys,xPlot3Value[0],xPlot3Value[1],xPlot3Value[2],xIsPlot3Name);

            xPlotKeys.clear();
            for(uint8_t i=0; i<3; ++i){
                xPlot1Value[i].clear();xPlot2Value[i].clear();xPlot3Value[i].clear();
            }
        }
        else if(!isStartCurvePlot)  //暂停显示
        {
            if(xPlotKeys.size() > 30000) //若下位机10ms发送一帧数据，则30000对应5min的数据  暂停了5分钟则自动清空
            {
                xPlotKeys.clear();
                for(uint8_t i=0; i<3; ++i){
                    xPlot1Value[i].clear();xPlot2Value[i].clear();xPlot3Value[i].clear();
                }
            }
        }
    }

    /* 5分钟清空一次绘图 */
    if( clearCurveTiming_3min == 30000 )
    {
//        clearCurveTiming_3min = 0;
        curvePlot->clearAllCurve_clicked();
    }
    clearCurveTiming_3min++;
    outStr.clear();

//    int debugTimeMs = debugTime.elapsed();
//    qDebug() << debugTimeMs;
}

quint8 MainWindow::parseOrSaveDataTask()                        //解析并保存数据
{
    // 线程资源同步
    rwLock.lockForRead();
    while (!threadBuffer.isEmpty())
        recvThreadDataBuffer.append( threadBuffer.dequeue() ); //读写锁同时到达 写锁优先级最高 所以一定为帧长的整数倍(包括帧头)
    rwLock.unlock();

    quint32 packetLen = recvThreadDataBuffer.size();
    if( packetLen == 0) return 0;  //有数据才解包

    QString str;
    quint16  xPacket = packetLen/frameLen;
    quint8* pBuf = recvThreadDataBuffer.data();    // 容器首地址 也就是帧头地址

    for(quint16 m=0; m<xPacket; ++m)  // 解m包
    {
        pBuf += frameHeaderLen;                    // 跳过帧头(没必要再判一次帧头了吧......)
        for(quint16 i=0; i<frameDataTypeLen; ++i)  // 解一包中的数据(类型)
        {
            quint8 tmp = frameDataType.at(i);
            switch(tmp)
            {
                case 0:{             //myChar  需与mydatatype的值对应！！！
                        qint8  tmp0 = qint8(*pBuf);                  pBuf += 1;
                        str = QString::number((int)tmp0);
                    } break;
                case 1:{              //myUint8_t
                        quint8 tmp1 = quint8(*pBuf);                 pBuf += 1;
                        str = QString::number((uint)tmp1);
                    } break;
                case 2:{              //myShort
                        qint16 tmp2 = CV.func_2bytesToShort(pBuf);   pBuf += 2;
                        str = QString::number((int)tmp2);
                    } break;
                case 3:{              //myUint16_t
                        quint16 tmp3 = CV.func_2bytesToUint16(pBuf); pBuf += 2;
                        str = QString::number((uint)tmp3);
                    } break;
                case 4:{              //myInt
                        qint32 tmp4 = CV.func_4bytesToInt(pBuf);     pBuf += 4;
                        str = QString::number((int)tmp4);
                    } break;
                case 5:{              //myUint32_t
                        quint32 tmp5 = CV.func_4bytesToUint32(pBuf); pBuf += 4;
                        str = QString::number((uint)tmp5);
                    } break;
                case 6:{              //myFloat
                        float  tmp6 = CV.func_4bytesToFloat(pBuf);   pBuf += 4;
                        str = QString::number((double)tmp6,'g',7);                  //float最大有效位数7位
                    } break;
                case 7:{              //myDouble
                        double tmp7 = CV.func_8bytesToDouble(pBuf);  pBuf += 8;
                        str = QString::number((double)tmp7,'g',15);                 //double最大有效位数15位
                    } break;
                case 8:{              //_3bytesToInt
                        qint32 tmp8 = CV.func_3bytesToInt(pBuf);     pBuf += 3;
                        str = QString::number((int)tmp8);
                    } break;
                case 9:{              //_2bytesToShort ? _2bytesToFloat
                        qint32 tmp9 = CV.func_2bytesToInt(pBuf);     pBuf += 2;
                        str = QString::number((int)tmp9);

                    } break;
            }
            outStr.append(str); //存起来(没存帧头) ①保存到文件 ②更新到表格
        }
        recv_frameCounter++;   //接收帧总计数
    }

    if(isSaveTxtFile) // 使能了文件保存
    {
        //解析数据保存为文本
        QTextStream txtOut(&txtFile);
        for(quint16 n=0; n<outStr.size(); ++n)
        {
            if( (n+1)%frameDataTypeLen == 0 )
            {
                txtOut << outStr.at(n) + "\n"; //全保存
//                for(uint8_t i=0; i<saveXDataTypeToTxt.size(); ++i)   //按选中保存逻辑还未实现
//                {
//                    txtOut << "%"+frameDataName.at( saveXDataTypeToTxt.at(i) )+" ";
//                }
            }
            else
            {
                txtOut << outStr.at(n) + " "; //全保存
            }
        }

        //原始数据保存为文本
        uint num = 0;
        QString strHex;
        QTextStream txtHexOut(&txtFileHex);
        for(quint16 m=0; m<packetLen; ++m)
        {
            num = recvThreadDataBuffer.at(m);
            if( num > 15){
                strHex = QString::number(num,16).toUpper();
            }else{
                strHex = "0" + QString::number(num,16).toUpper();
            }

            if( (m+1)%frameLen == 0 ) {
                txtHexOut << strHex + "\n";    //一帧的最后一个字节 补换行
            }else{
                txtHexOut << strHex + " ";
            }
        }
    }
    recvThreadDataBuffer.clear();  //保存完原始数据清空

    return xPacket;
}

void MainWindow::refreshTableTask(quint8 xPacket)               //刷新表格数据显示
{
    if(xPacket == 0) return;
    quint16 tmp = (xPacket - 1) * frameDataTypeLen; // 只取最后一包数据显示在表格内

    for(quint16 i=frameHeaderLen,cnt = tmp; i<ui->tableWidget->rowCount(); i++,cnt++)
    {
        ui->tableWidget->item(i,colData)->setData(Qt::DisplayRole,outStr.at(cnt));
    }
}

/**************************************************** 协议帧 *********************************************************/

void MainWindow::creatItemsARow(int curRow, QString name, QString type, QString data, QString Curve)
{
  QFont font;
  font.setFamily("Times New Roman");
  font.setPointSize(13);
  QTableWidgetItem *item;

  //名称
  item = new QTableWidgetItem(name,ctName);
  ui->tableWidget->setItem(curRow,colName,item);
  item->setFont(font);  item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
  if( (name == "Frame_Header") || (name == "Frame_Tail") || (name == "Check_Sum") || (name == "Time_Stamp")) // 固定名称不可编辑
  {
      item->setFlags( item->flags() & (~Qt::ItemIsEditable));
  }
  if( (name != "Frame_Header") && (name != "Frame_Tail") && (name != "Check_Sum")) //名称前加复选框 用于保存
  {
      item->setCheckState(Qt::Checked);
  }

  //数据类型
  item = new QTableWidgetItem(type,ctType);
  ui->tableWidget->setItem(curRow,colType,item);
  item->setFont(font);  item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
  if( (name == "Frame_Header") || (name == "Frame_Tail"))        // 固定名称不可编辑
  {
      item->setFlags( item->flags() & (~Qt::ItemIsEditable));
  }

  //数据
  item = new QTableWidgetItem(data,ctData);
  ui->tableWidget->setItem(curRow,colData,item);
  item->setFont(font);  item->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);

  //绘图1
  item = new QTableWidgetItem(Curve,ctIsCurve);
  ui->tableWidget->setItem(curRow,colIsCurve,item);
  if( (name == "Frame_Header") || (name == "Frame_Tail") || (name == "Check_Sum")){ // 固定名称不可绘图
      item->setText("--");
      item->setFlags(Qt::NoItemFlags);
  }else{
      item->setCheckState(Qt::Unchecked);
      item->setFlags(item->flags() & ~Qt::ItemIsEditable & ~Qt::ItemIsSelectable);
  }
  item->setFont(font);

  //绘图2
  item = new QTableWidgetItem(Curve,ctIsCurve1);
  ui->tableWidget->setItem(curRow,colIsCurve1,item);
  if( (name == "Frame_Header") || (name == "Frame_Tail") || (name == "Check_Sum")){ // 固定名称不可绘图
      item->setText("--");
      item->setFlags(Qt::NoItemFlags);
  }else{
      item->setCheckState(Qt::Unchecked);
      item->setFlags(item->flags()&~Qt::ItemIsEditable & ~Qt::ItemIsSelectable);  //不可选中 不可编辑
  }
  item->setFont(font);

  //绘图3
  item = new QTableWidgetItem(Curve,ctIsCurve2);
  ui->tableWidget->setItem(curRow,colIsCurve2,item);
  if( (name == "Frame_Header") || (name == "Frame_Tail") || (name == "Check_Sum")){ // 固定名称不可绘图
      item->setText("--");
      item->setFlags(Qt::NoItemFlags);
  }else{
      item->setCheckState(Qt::Unchecked);
      item->setFlags(item->flags()&~Qt::ItemIsEditable & ~Qt::ItemIsSelectable);  //不可选中 不可编辑
  }
  item->setFont(font);
}

void MainWindow::on_confirmDataFrame_btn_clicked(bool checked)  //确认数据帧
{
  if(checked)
  {
      ui->confirmDataFrame_btn->setText("编辑数据帧");
      /* 失能所有按键 失能表格编辑功能并遍历元素 */
      isEnabledAllFrameBtn(false);
      ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);

      bool ok;
      int tmp = 0;
      QString str;

      frameLen = 0;
      frameHeaderLen = 0;
      frameDataTypeLen = 0;
      checkSumPosOnframeDataType = -1;    //默认没有校验和
      TimeStampPosOnframeDataType = -1;    //默认没有时间戳

      frameTailNum.clear();
      frameDataType.clear();
      frameDataName.clear();
      frameHeaderNum.clear();
      dataTypeDivisorFactor.clear();

      // 遍历表格所有行
      for(uint8_t i=0; i<ui->tableWidget->rowCount(); i++)
      {
          str = ui->tableWidget->item(i,colName)->text();

          // 帧头
          if( str == "Frame_Header")
          {
              tmp = ui->tableWidget->item(i,colData)->text().toInt(&ok,16);
              if((tmp > 255) || (ok == false)){
                  QMessageBox::critical(this,"@!!!!","帧头值设置错误..........");return;
              }
              frameHeaderNum.append( (uint8_t)tmp );
              frameLen += 1;
          }

          // 帧尾 (用处不大)
          else if( str == "Frame_Tail")
          {
              tmp = ui->tableWidget->item(i,colData)->text().toInt(&ok,16);
              if((tmp > 255) || (ok == false)){
                  QMessageBox::critical(this,"@!!!!","帧尾值设置错误..........");return;
              }
              frameTailNum.append( (uint8_t)tmp );
              frameLen += 1;

              /* 检验帧尾是否处于最末端 */
              if( i != (ui->tableWidget->rowCount()-1) ){
                  QMessageBox::critical(this,"@!!!!","帧尾不在末端？..........");return;
              }
          }

          // 数据区(时间戳 + 数据 + 校验)
          else
          {
              //将数据区的所有名字保存，用于作存储数据时的表头
              frameDataName.append(str);

              // 时间戳
              if( str == "Time_Stamp" )
              {
                  str = ui->tableWidget->item(i,colType)->text();
                  if     (str == myDataTypeList.at(1))  { frameLen += 1; frameDataType.append(myUint8_t );}
                  else if(str == myDataTypeList.at(3))  { frameLen += 2; frameDataType.append(myUint16_t);}
                  else if(str == myDataTypeList.at(5))  { frameLen += 4; frameDataType.append(myUint32_t);}
                  else if(str == myDataTypeList.at(6))  { frameLen += 4; frameDataType.append(myFloat);   }
                  else {
                      QMessageBox::critical(this,"@!!!!","时间戳数据类型不支持......");return;
                  }
                  TimeStampPosOnframeDataType = frameDataType.count() - 1;  //时间戳在数据区中的位置
//                  qDebug() << "时间戳在数据区中的位置" << TimeStampPosOnframeDataType;
              }
              // 和校验
              else if( str == "Check_Sum")
              {
                  str = ui->tableWidget->item(i,colType)->text();
                  if     (str == myDataTypeList.at(1))  { frameLen += 1; frameDataType.append(myUint8_t );}
//                  else if(str == myDataTypeList.at(3))  { frameLen += 2; frameDataType.append(myUint16_t);}
                  else if(str == myDataTypeList.at(5))  { frameLen += 4; frameDataType.append(myUint32_t);}
                  else {
                      QMessageBox::critical(this,"@!!!!","和校验数据类型不支持......");return;
                  }
                  checkSumPosOnframeDataType = frameDataType.count() - 1;  //校验和在数据区中的位置
//                  qDebug() << "校验和在数据区中的位置" << checkSumPosOnframeDataType;
              }
              // 数据
              else
              {
                  str = ui->tableWidget->item(i,colType)->text();
                  if(str == myDataTypeList.at(0))       { frameDataType.append(myChar);    frameLen += 1;}
                  else if(str == myDataTypeList.at(1))  { frameDataType.append(myUint8_t); frameLen += 1;}
                  else if(str == myDataTypeList.at(2))  { frameDataType.append(myShort);   frameLen += 2;}
                  else if(str == myDataTypeList.at(3))  { frameDataType.append(myUint16_t);frameLen += 2;}
                  else if(str == myDataTypeList.at(4))  { frameDataType.append(myInt);     frameLen += 4;}
                  else if(str == myDataTypeList.at(5))  { frameDataType.append(myUint32_t);frameLen += 4;}
                  else if(str == myDataTypeList.at(6))  { frameDataType.append(myFloat);   frameLen += 4;}
                  else if(str == myDataTypeList.at(7))  { frameDataType.append(myDouble);  frameLen += 8;}
                  else if(str == myDataTypeList.at(8))  //一般的数据手册 都会除以一个比例尺 这里对应dataTypeDivisorFactor
                  {
                      frameDataType.append(_3bytesToInt);
//                      tmp = ui->tableWidget->item(i,colData)->text().toInt(&ok,10);
//                      if((tmp > 32) || (tmp < -32) || (ok == false)){
//                          QMessageBox::critical(this,"@!!!!","_3bytesToInt类型的除数设置错误");return;
//                      }
//                      dataTypeDivisorFactor.append(tmp);
                      frameLen += 3;
                  }
                  else if(str == myDataTypeList.at(9))
                  {
                      frameDataType.append(_2bytesToInt);
//                      tmp = ui->tableWidget->item(i,colData)->text().toInt(&ok,10);
//                      if((tmp > 32) || (tmp < -32) || (ok == false)){
//                          QMessageBox::critical(this,"@!!!!","_2bytesToInt类型的除数设置错误");return;
//                      }
//                      dataTypeDivisorFactor.append(tmp);
                      frameLen += 2;
                  }
              }
          }
      }
      frameHeaderLen = frameHeaderNum.size();
      frameDataTypeLen = frameDataType.size();
      ui->label_showFrameLen->setText(QString::number(frameLen));
      emit signal_sendFrameInfoToSerialSubThread(frameLen,frameHeaderNum);

/***************************************************帧信息*******************************************************/
//      qDebug() << "帧长" << frameLen << "帧头长度" << frameHeaderLen << "数据区长度" << frameDataTypeLen;
//      qDebug() << "帧头值";
//      for(uint8_t i=0; i<frameHeaderNum.size(); ++i)
//      {
//          qDebug() << frameHeaderNum.at(i);
//      }
//      qDebug() << "数据名" << "数据类型";
//      for(uint8_t i=0; i<frameDataName.size(); ++i)
//      {
//          qDebug() << frameDataName.at(i) << frameDataType.at(i);
//      }
//      qDebug() << "校验位在数据区的位置" << checkSumPosOnframeDataType << "校验类型" << frameDataType.at(checkSumPosOnframeDataType);
  }
  else
  {
      ui->confirmDataFrame_btn->setText("确认数据帧");
      /* 使能所有按键  使能表格编辑功能 */
      isEnabledAllFrameBtn(true);
      ui->tableWidget->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::DoubleClicked);
  }
}

void MainWindow::isEnabledAllFrameBtn(bool checked)             //使/失能协议帧相关按键
{
  ui->addRow_btn->setEnabled(checked);
  ui->deleteRow_btn->setEnabled(checked);
  ui->insertRow_btn->setEnabled(checked);
  ui->loadDataFrame_btn->setEnabled(checked);
  ui->clearDataFrame_btn->setEnabled(checked);
  ui->insertCheckSum_btn->setEnabled(checked);
  ui->insertTimeStamp_btn->setEnabled(checked);
  ui->insertFrameHeader_btn->setEnabled(checked);
//  ui->insertFrameTail_btn->setEnabled(checked);    //不失能插入帧尾
//  ui->saveDataFrame_btn->setEnabled(checked);
}

void MainWindow::on_insertRow_btn_clicked()
{
  int curRow = ui->tableWidget->currentRow();
  ui->tableWidget->insertRow(curRow);
  creatItemsARow(curRow);
}

void MainWindow::on_addRow_btn_clicked()
{
  int curRow = ui->tableWidget->rowCount();
  ui->tableWidget->insertRow(curRow);
  creatItemsARow(curRow);
}

void MainWindow::on_insertFrameHeader_btn_clicked()
{
  int curRow = ui->tableWidget->currentRow();
  ui->tableWidget->insertRow(curRow);
  creatItemsARow(curRow,"Frame_Header","uint8_t(hex)","<keyboard Input>");
}

void MainWindow::on_insertFrameTail_btn_clicked()
{
  int curRow = ui->tableWidget->rowCount();
  ui->tableWidget->insertRow(curRow);
  creatItemsARow(curRow,"Frame_Tail","uint8_t(hex)","<keyboard Input>");
}

void MainWindow::on_insertTimeStamp_btn_clicked()
{
  int curRow = ui->tableWidget->currentRow();
  ui->tableWidget->insertRow(curRow);
  creatItemsARow(curRow,"Time_Stamp");
}

void MainWindow::on_insertCheckSum_btn_clicked()
{
  int curRow = ui->tableWidget->currentRow();
  ui->tableWidget->insertRow(curRow);
  creatItemsARow(curRow,"Check_Sum");
}

void MainWindow::on_deleteRow_btn_clicked()
{
  int curRow = ui->tableWidget->currentRow();
  ui->tableWidget->removeRow(curRow);
}

void MainWindow::on_clearDataFrame_btn_clicked()                //清除所有数据帧
{
  for(int i = (ui->tableWidget->rowCount()-1); i>=0; i--)
  {
      ui->tableWidget->removeRow(i);
  }
}

/***************************************************** 串口 *********************************************************/

void MainWindow::slot_switchSerialPort(bool checked)            //打开/关闭串口
{
    //无论什么操作均将队列清空
    threadBuffer.clear();
    xPlotKeys.clear();
    for(uint8_t i=0; i<3; ++i){
        xPlot1Value[i].clear();xPlot2Value[i].clear();xPlot3Value[i].clear();
    }

    if(checked)//打开串口
    {
        QString portxName     = ui->setPort_combox->currentText();
        qint32  portxBaudrate = ui->setBaud_combox->currentText().toInt();
        qint8   portxDataBits = ui->setDataBits_combox->currentText().toUShort();
        qint8   portxStopBits = ui->setStopBits_combox->currentIndex();
        qint8   portxParity   = ui->setParity_combox->currentIndex();

        switch(portxStopBits)
        {
            case 0: portxStopBits = 1; break;  // 1停止位
            case 1: portxStopBits = 3; break;  // 1.5
            case 2: portxStopBits = 2; break;  // 2
            default:portxStopBits = 1;
        }
        switch(portxParity)
        {
            case 0: portxParity = 0; break;   //无校验
            case 1: portxParity = 2; break;   //偶校验
            case 2: portxParity = 3; break;   //奇
            default:portxParity = 0;
        }
        emit signal_openSerialPortx(portxName,portxBaudrate,portxDataBits,portxParity,portxStopBits);
    }
    else       //关闭串口
    {
        runTimer->stop();    //关闭串口停止计时
        emit signal_closeSerialPortx();
        ui->curveShow_btn->setChecked(false);           //关闭串口-->关闭曲线Ui
        ui->dashboardShow_btn->setChecked(false);       //关闭串口-->关闭仪表盘Ui
//        if(isSaveTxtFile){ }                            //关闭串口-->关闭数据捕获功能??
    }
}

void MainWindow::slot_showSerialPortxOpenStatus(bool checked)   //显示串口打开状态
{
  if(checked) {
      isEnabledSerialPortPanelBtn(false);
      ui->serialPortStauts_led->setRed();

      outStr.clear();
      timing10msCounter = 0;
      recv_frameCounter = 0;
      runTimer->start();    //打开成功开始计时
  }else{
      isEnabledSerialPortPanelBtn(true);
      ui->serialPortStauts_led->setBgColor(QColor("#6f7a7e"));
      ui->switchSerialPort_btn->setChecked(false);
      QMessageBox::critical(this,"@!!!!","串口打开失败或已被占用......");
  }
}

void MainWindow::slot_showSerialPortxCloseStauts(bool checked)  //显示串口关闭状态
{
  if(checked) {
      isEnabledSerialPortPanelBtn(true);
      ui->serialPortStauts_led->setBgColor(QColor("#6f7a7e"));
  }else{
      isEnabledSerialPortPanelBtn(false);
      ui->serialPortStauts_led->setRed();
      QMessageBox::critical(this,"@!!!!","串口关闭失败......");
  }
}

void MainWindow::isEnabledSerialPortPanelBtn(bool checked)      //使/失能串口相关按钮
{
  ui->setPort_combox->setEnabled(checked);
  ui->setBaud_combox->setEnabled(checked);
  ui->setParity_combox->setEnabled(checked);
  ui->setDataBits_combox->setEnabled(checked);
  ui->setStopBits_combox->setEnabled(checked);
  ui->confirmDataFrame_btn->setEnabled(checked);  //打开串口后 数据帧面板使/失能
  ui->comboBox_setEndianMode->setEnabled(checked);  //打开串口后 大小端模式使/失能
}

/***************************************************** 文件 *********************************************************/

void MainWindow::on_saveData_btn_clicked()                      //捕获数据
{
    if(!isSaveTxtFile)
    {
        /* 建立一个子文件夹 用于存放txt文件 */
        QString curPath = QDir::currentPath();
        QDir dir(curPath);
        QString savePath = curPath+ "/txtFile";
        if(!dir.exists(savePath))  {
            dir.mkdir("txtFile");
        }
        QString dlgTitle = tr("另存为");
//        QString filter = "文本文件(*.txt);;所有文件(*.)";
        QString filter = "文本文件(*.txt)";
        QString saveFilename = QFileDialog::getSaveFileName(this,dlgTitle,savePath,filter);

        if (saveFilename.isEmpty())
        {
            ui->saveData_btn->setChecked(false);
            return ;
        }

//        saveXDataTypeToTxt.clear();
//        // 遍历状态：该数据类型是否需要保存到txt
//        for(quint16 i=0; i<ui->tableWidget->rowCount(); i++)
//        {
//            if(ui->tableWidget->item(i,colName)->checkState() == Qt::Checked){
//                saveXDataTypeToTxt.append(ui->tableWidget->row(ui->tableWidget->item(i,colName)) - frameHeaderLen);  //对应了数据类型的位置
//            }
//        }

        txtFile.setFileName(saveFilename);                  //解析数据
        txtFileHex.setFileName(saveFilename.remove(".txt") + "Hex.txt");         //原始数据

        QTextStream txtOut(&txtFile);
//        QTextStream txtHexOut(&txtFileHex);     //这里只打开，不存数据
        if(txtFile.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate )) //如果文件存在则打开,不存在创建并打开 打开后在原有内容后开始写入
        {
            ui->saveData_btn->setText("停止捕获");
            ui->saveData_btn->setStyleSheet("QPushButton{color:rgb(170,0,0,255);}");

            for(uint8_t i=0; i<frameDataName.size(); ++i)
            {
                txtOut << "%"+frameDataName.at(i)+" ";
            }
            txtOut << "\n";

//            //按选中保存数据
//            for(uint8_t i=0; i<saveXDataTypeToTxt.size(); ++i)
//            {
//                txtOut << "%"+frameDataName.at( saveXDataTypeToTxt.at(i) )+" ";
//            }
//            txtOut << "\n";

            isSaveTxtFile = true;
            ui->label_showFileInfo->setText("数据正在保存.........");
        }
        if(txtFileHex.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate )) //如果文件存在则打开,不存在创建并打开 打开后在原有内容后开始写入
        {
            ui->label_showFileInfo->setText("数据正在保存......！！！");
        }
    }
    else{
        int ret = QMessageBox::question(this, "question","确定要停止捕获数据???",QMessageBox::Yes|QMessageBox::No,QMessageBox::No);
        if(ret == QMessageBox::Yes)
        {
            txtFile.close();
            txtFileHex.close();
            isSaveTxtFile = false;
            ui->saveData_btn->setText("捕获数据");
            ui->saveData_btn->setStyleSheet("QPushButton{color:rgb(0,0,0,255);}");
            ui->label_showFileInfo->setText("数据保存成功.........");
        }
    }
}

void MainWindow::on_saveDataFrame_btn_clicked()                 //保存数据帧配置
{
    /* 建立一个子文件夹 用于存放文件 */
    QString curPath = QDir::currentPath();
    QDir dir(curPath);
    QString savePath = curPath+ "/Config";
    if(!dir.exists(savePath))  {
        dir.mkdir("Config");
    }
    QString dlgTitle = tr("另存为");
    QString filter = "文本文件(*.txt);;所有文件(*.)";
    QString saveFilename = QFileDialog::getSaveFileName(this,dlgTitle,savePath,filter);

    if (saveFilename.isEmpty())
        return ;

    /* 写文件 */
    QFile configFile(saveFilename);
    if(configFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) //如果文件存在则打开,不存在创建并打开,打开后清空原有数据
    {
        //遍历表格
        QString str;
        QTextStream out(&configFile);
        for(uint8_t i=0; i<ui->tableWidget->rowCount(); i++)
        {
            str = ui->tableWidget->item(i,colName)->text(); out << str << ",";
            str = ui->tableWidget->item(i,colType)->text(); out << str << ",";
            str = ui->tableWidget->item(i,colData)->text(); out << str << "\n";
        }
        configFile.close();
        ui->label_showFileInfo->setText("数据协议帧已保存.........");
    }
}

void MainWindow::on_loadDataFrame_btn_clicked()                 //加载数据帧配置
{
    /* 建立一个子文件夹 用于存放文件 */
    QString curPath = QDir::currentPath();
    QDir dir(curPath);
    QString savePath = curPath+ "/Config";
    if(!dir.exists(savePath))  {
        dir.mkdir("Config");
    }
    QString dlgTitle = tr("加载配置文件");
    QString filter = "文本文件(*.txt);;所有文件(*.)";
    QString loadFilename = QFileDialog::getOpenFileName(this,dlgTitle,savePath,filter);
    if (loadFilename.isEmpty())
        return ;

    /* 写文件 */
    QFile configFile(loadFilename);
    if(configFile.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        //遍历表格
        QString strLine;
        QTextStream in(&configFile);
        /* 先清空表格 */
        this->on_clearDataFrame_btn_clicked();

        for(uint8_t i=0; in.atEnd() == false; i++)
        {
            strLine = in.readLine();
            QStringList strList = strLine.split(",");

            /* 添加表格 */
            int curRow = ui->tableWidget->rowCount();
            ui->tableWidget->insertRow(curRow);
            this->creatItemsARow(i,strList.at(0),strList.at(1),strList.at(2));
        }
        configFile.close();
        ui->label_showFileInfo->setText("数据协议帧加载成功.........");
    }
}

/****************************************************** UI *********************************************************/

void MainWindow::on_curveShow_btn_toggled(bool checked)         //曲线UI
{
    QVector<quint8> VecEmpty;
    //打开曲线显示 清除原有图像
    if(checked){
        if(!isOpenCurvePlot)
        {
            detectAllCurvePlotCheckBox();
            curvePlot->clearAllCurve_clicked();
            curvePlot->move(this->pos().x()+200,this->pos().y()+66);
            curvePlot->resize(800,510);
            curvePlot->show();
            isStartCurvePlot = true;
            isOpenCurvePlot = true;

            curvePlot->resetAllBtn();
            ui->comboBox_setXAxis->setEnabled(false);  //X轴属性不可调节
            isEnableCurvePlotCheckBox( VecEmpty,colIsCurve ,false); //失能所有复选框
            isEnableCurvePlotCheckBox( VecEmpty,colIsCurve1,false); //失能所有复选框
            isEnableCurvePlotCheckBox( VecEmpty,colIsCurve2,false); //失能所有复选框
        }
    }else{
        if(isOpenCurvePlot)
        {
//            qDebug() << "关闭绘图显示";
            isOpenCurvePlot = false;
            isStartCurvePlot = false;
            curvePlot->clearAllCurve_clicked();
            curvePlot->close();

            ui->comboBox_setXAxis->setEnabled(true);  //X轴属性可调节
            isEnableCurvePlotCheckBox( VecEmpty,colIsCurve ,true); //使能所有复选框
            isEnableCurvePlotCheckBox( VecEmpty,colIsCurve1,true); //使能所有复选框
            isEnableCurvePlotCheckBox( VecEmpty,colIsCurve2,true); //使能所有复选框
            if(isPlotXyz.size() >= 3) isEnableCurvePlotCheckBox(isPlotXyz,colIsCurve,false); //失能除选中之外的所有复选框
            if(isPlotxYz.size() >= 3) isEnableCurvePlotCheckBox(isPlotxYz,colIsCurve1,false); //失能除选中之外的所有复选框
            if(isPlotxyZ.size() >= 3) isEnableCurvePlotCheckBox(isPlotxyZ,colIsCurve2,false); //失能除选中之外的所有复选框
        }
    }
}

void MainWindow::on_dashboardShow_btn_toggled(bool checked)     //仪表盘UI
{
    if(checked)
    {
        if(!isOpenDashBoard)
        {
            detectEulerAnglesToDashBoard();
            attDashBoard->move(this->pos().x()+200,this->pos().y()+150);
            attDashBoard->show();
            attDashBoard->setValue(10.5,0.5,0.5);
            isOpenDashBoard = true;
        }
    }
    else
    {
        if(isOpenDashBoard)
        {
//            qDebug() << "关闭仪表盘显示";
            attDashBoard->close();
            isOpenDashBoard = false;
        }
    }
}

void MainWindow::on_showHelp_btn_clicked()                      //帮助UI
{
    if(!isOpenHelpUi){
        helpInfo->show();
        isOpenHelpUi = true;
    }else{
        helpInfo->close();
        isOpenHelpUi = false;
    }
}

